package com.starry.module.system.core.menu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.starry.core.common.constants.CommonError;
import com.starry.core.common.domain.vo.PageVo;
import com.starry.core.common.execption.ServiceException;
import com.starry.core.common.tenant.context.TenantContextHolder;
import com.starry.core.security.context.SecurityInfoContext;
import com.starry.core.security.domain.BaseUser;
import com.starry.db.core.enums.DBEnableDisableStatusEnum;
import com.starry.db.core.query.LambdaQueryWrapperX;
import com.starry.module.system.api.menu.domain.qo.SysMenuPageQo;
import com.starry.module.system.api.menu.domain.qo.SysMenuQo;
import com.starry.module.system.api.menu.domain.qo.SysMenuTreeQo;
import com.starry.module.system.api.menu.domain.vo.LoginRouterVo;
import com.starry.module.system.api.menu.domain.vo.SysMenuDtlVo;
import com.starry.module.system.api.menu.domain.vo.SysMenuTreeVo;
import com.starry.module.system.api.menu.domain.vo.SysMenuVo;
import com.starry.module.system.core.menu.entity.SysMenu;
import com.starry.module.system.core.menu.mapper.SysMenuMapper;
import com.starry.module.system.core.menu.service.SysMenuService;
import com.starry.module.system.core.tenant.service.SysTenantService;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 系统菜单-服务实现
 *
 * @author xiaoke
 */
@Service("sysMenuService")
public class SysMenuServiceImpl implements SysMenuService {

    private final SysMenuMapper sysMenuMapper;
    private final SysTenantService sysTenantService;

    @Lazy
    public SysMenuServiceImpl(SysMenuMapper sysMenuMapper, SysTenantService sysTenantService) {
        this.sysMenuMapper = sysMenuMapper;
        this.sysTenantService = sysTenantService;
    }

    @Override
    public PageVo<SysMenuVo> page(SysMenuPageQo pageQo) {
        PageVo<SysMenu> page = sysMenuMapper.selectPage(pageQo);
        List<SysMenuVo> vos = BeanUtil.copyToList(page.getRows(), SysMenuVo.class);
        return new PageVo<>(vos, page.getTotal());
    }

    @Override
    public SysMenuDtlVo getById(Long id) {
        SysMenu sysMenu = sysMenuMapper.selectById(id);
        if (sysMenu == null) {
            throw new ServiceException(CommonError.DATA_NOT_EXISTS.getMsg());
        }
        return BeanUtil.toBean(sysMenu, SysMenuDtlVo.class);
    }

    @Transactional
    @Override
    public Long save(SysMenuQo sysMenuQo) {
        LambdaQueryWrapperX<SysMenu> wrapper = new LambdaQueryWrapperX<>();
        wrapper
                .eq(SysMenu::getName, sysMenuQo.getName());
        if (sysMenuMapper.selectCount(wrapper) > 0) {
            throw new ServiceException(CommonError.DATA_EXISTS.getMsg());
        }
        SysMenu sysMenu = BeanUtil.toBean(sysMenuQo, SysMenu.class);
        sysMenu.setId(null);
        sysMenuMapper.insert(sysMenu);
        return sysMenu.getId();
    }

    @Override
    public Set<String> getAllPerms() {
        return sysMenuMapper.getAllPerms();
    }

    @Override
    public boolean existMenuList(Set<Long> menuIdList) {
        if (menuIdList.isEmpty()) return true;
        LambdaQueryWrapperX<SysMenu> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper
                .in(SysMenu::getId, menuIdList);
        return sysMenuMapper.selectCount(queryWrapper) == menuIdList.size();
    }

    @Transactional
    @Override
    public void updateById(SysMenuQo sysMenuQo) {
        if (sysMenuQo.getId() == null) {
            throw new ServiceException(CommonError.DATA_NOT_EXISTS.getMsg());
        }
        LambdaQueryWrapperX<SysMenu> wrapper = new LambdaQueryWrapperX<>();
        wrapper
                .ne(SysMenu::getId, sysMenuQo.getId())
                .and(w ->
                        w.eq(SysMenu::getName, sysMenuQo.getName())
                );
        if (sysMenuMapper.selectCount(wrapper) > 0) {
            throw new ServiceException(CommonError.DATA_EXISTS.getMsg());
        }
        SysMenu sysMenu = sysMenuMapper.selectById(sysMenuQo.getId());
        if (sysMenu == null) {
            throw new ServiceException(CommonError.DATA_NOT_EXISTS.getMsg());
        }
        BeanUtil.copyProperties(sysMenuQo, sysMenu);

        if (sysMenu.getPid() == null) {
            sysMenu.setPid(0L);
        }

        sysMenuMapper.updateById(sysMenu);
    }

    @Transactional
    @Override
    public void removeByIds(Set<Long> idList) {
        if (idList.isEmpty()) return;
        sysMenuMapper.deleteByIds(idList);
    }

    @Transactional
    @Override
    public void enable(Set<Long> idList) {
        if (idList.isEmpty()) return;
        sysMenuMapper.updateBatch(setMenuStatus(idList, DBEnableDisableStatusEnum.ENABLE));
    }

    @Transactional
    @Override
    public void disable(Set<Long> idList) {
        if (idList.isEmpty()) return;
        sysMenuMapper.updateBatch(setMenuStatus(idList, DBEnableDisableStatusEnum.DISABLE));
    }

    @Override
    public List<LoginRouterVo> getByLoginUserMenuRouter() {
        BaseUser loginUser = SecurityInfoContext.getLoginUser();
        List<LoginRouterVo> menuRouter = sysMenuMapper.getByLoginUserMenuRouter(loginUser.getId());
        return getRouterMenuTree(0L, menuRouter);
    }

    @Override
    public List<SysMenuTreeVo> getMenuTree(SysMenuTreeQo treeQo) {
        List<SysMenu> treeMenuList = sysMenuMapper.getTreeMenuList(treeQo);
        if (treeMenuList.isEmpty()) return List.of();
        List<SysMenuTreeVo> menuTree = BeanUtil.copyToList(treeMenuList, SysMenuTreeVo.class);
        return getMenuTree(0L, menuTree);
    }

    @Override
    public List<SysMenuTreeVo> getTenantMenuTree(SysMenuTreeQo treeQo) {
        // 如果是系统租户，则获取全部菜单树
        if (TenantContextHolder.validateIsSystemTenant()) {
            return getMenuTree(treeQo);
        }
        Long tenantId = TenantContextHolder.getRequiredTenantId();
        Set<Long> tenantPackageMenuIds = sysTenantService.getTenantPackageMenuIds(tenantId);

        if (tenantPackageMenuIds.isEmpty()) return List.of();

        List<SysMenu> treeTenantMenuList = sysMenuMapper.getTreeTenantMenuList(treeQo, tenantPackageMenuIds);

        if (treeTenantMenuList.isEmpty()) return List.of();

        List<SysMenuTreeVo> menuTree = BeanUtil.copyToList(treeTenantMenuList, SysMenuTreeVo.class);
        return getMenuTree(0L, menuTree);
    }

    private Set<SysMenu> setMenuStatus(Set<Long> idList, DBEnableDisableStatusEnum status) {
        Set<SysMenu> sysMenus = new HashSet<>();
        idList.forEach(item -> {
            SysMenu sysMenu = new SysMenu();
            sysMenu.setId(item);
            sysMenu.setStatus(status.getCode());
            sysMenus.add(sysMenu);
        });
        return sysMenus;
    }

    /**
     * 递归获取路由菜单树
     *
     * @param pid   父id
     * @param menus 菜单列表
     * @return List<LoginRouterVo>
     */
    private List<LoginRouterVo> getRouterMenuTree(Long pid, List<LoginRouterVo> menus) {
        List<LoginRouterVo> children = new ArrayList<>();
        for (LoginRouterVo menu : menus) {
            if (menu.getPid().equals(pid)) {
                children.add(menu);
                // 递归调用，获取子菜单的子菜单
                menu.setChildren(getRouterMenuTree(menu.getId(), menus));
            }
        }
        return children.stream()
                .sorted(Comparator.comparingInt(m -> m.getMeta().getMenuRank()))
                .collect(Collectors.toList());
    }

    /**
     * 递归获取菜单树
     *
     * @param pid   父id
     * @param menus 菜单列表
     * @return List<SysMenuTreeVo>
     */
    private List<SysMenuTreeVo> getMenuTree(Long pid, List<SysMenuTreeVo> menus) {
        List<SysMenuTreeVo> children = new ArrayList<>();
        for (SysMenuTreeVo menu : menus) {
            if (menu.getPid().equals(pid)) {
                children.add(menu);
                // 递归调用，获取子菜单的子菜单
                menu.setChildren(getMenuTree(menu.getId(), menus));
            }
        }
        return children.stream()
                .sorted(Comparator.comparingInt(SysMenuTreeVo::getMenuRank))
                .collect(Collectors.toList());
    }

}

